package bsh;

import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import bsh.asm.acessor.DynamicFieldAccessor;
import bsh.asm.acessor.FieldObjectAcessor;
import bsh.asm.acessor.ObjectAccessor;
import bsh.asm.acessor.ThisObjectAccessor;
import bsh.compilation.DebugOptions;
import bsh.compilation.ReflectionUtilsImpl;
import bsh.exception.CompilationAssertionFailed;
import bsh.exception.CompilationFailedException;
import bsh.meta.AbstractMethodDescriptor;
import bsh.meta.ArrayTypeWrapper;
import bsh.meta.FieldDescriptor;
import bsh.meta.JavaClassWrapper;
import bsh.meta.MethodDescriptor;
import bsh.meta.Scope;
import bsh.meta.TypeTransformation;
import bsh.meta.TypeWrapper;
import bsh.meta.UnfinishedInnerType;
import bsh.meta.VoidTypeWrapper;
import bsh.utils.reflection.BytecodeHelper;

/**
 * MethodCallCompiler
 * 
 * @author takeshi
 */
public class MethodCallCompiler implements NodeCompiler {

    public static final MethodDescriptor LOG_METHOD_INVOCATION = new ReflectionUtilsImpl().findExactMatch(
            JavaClassWrapper.wrapperFor(CompiledScriptHelper.class), "registerMethodInvocation", new TypeWrapper[]{
                    JavaClassWrapper.OBJECT_WRAPPER, JavaClassWrapper.STRING_WRAPPER, JavaClassWrapper.wrapperFor(Class.class),
                    JavaClassWrapper.STRING_WRAPPER, JavaClassWrapper.INT_WRAPPER, JavaClassWrapper.INT_WRAPPER,
                    JavaClassWrapper.BOOLEAN_WRAPPER, JavaClassWrapper.INT_WRAPPER });

    private static final int NUM_3 = 3;

    private final BSHMethodInvocation node;

    private final CompilationUnit unit;

    /**
     * @param node node
     * @param unit unit
     */
    public MethodCallCompiler(final BSHMethodInvocation node, final CompilationUnit unit) {
        super();
        this.unit = unit;
        this.node = node;
    }

    /**
     * {@inheritDoc}
     */
    public void compile(final MethodVisitor host, final CompilationState state) throws CompilationFailedException {
        final BSHArguments argsNode = this.node.getArgsNode();
        final BSHAmbiguousName nameNode = this.node.getNameNode();
        if (this.isConstructorCall(nameNode)) {
            compileConstructorCall(host, state, argsNode, nameNode);
            return;
        } else if (this.isUnqualifiedMethodCall(nameNode)) {
            compileUnqualifiedMethodCall(host, state, argsNode, nameNode);
            return;
        }
        final MethodDescriptor targetMethod = this.resolveMethod(nameNode, argsNode, state.currentScope());
        logMethodInvocation(unit, node, host, state, argsNode, targetMethod);
        if (targetMethod instanceof DynamicMethodDescriptor) {
            compileInvokeDynamic(host, state, argsNode, nameNode, targetMethod, (DynamicMethodDescriptor) targetMethod);
        } else {
            final TypeWrapper methodTargetType = targetMethod.declaringType();
            final boolean isStatic = Modifier.isStatic(targetMethod.modifiers());
            if (!isStatic) {
                pushCallTarget(host, nameNode, state, methodTargetType);
            }
            final int methodTargetOffset = isStatic ? 0 : 1;
            state.assertMinimumStackSize(methodTargetOffset);
            if (!isStatic) {
                TypeWrapper top = state.peekStack();
                if (!methodTargetType.isSuperTypeOf(top)) {
                    throw new CompilationAssertionFailed("trying to call method from: " + methodTargetType + " on type: " + top);
                }
            }
            final List<? extends TypeWrapper> paramTypes = targetMethod.parameterTypes();
            if (!paramTypes.isEmpty()) {
                pushArgumentsFor(host, argsNode, paramTypes, state, this.unit, targetMethod.isVarArgs());
            }
            final int toPop = methodTargetOffset + paramTypes.size();
            state.assertMinimumStackSize(toPop);

            final int stackSize = state.getStackSize();

            state.addLineNumber(host, this.node.lastToken.beginLine);
            targetMethod.writeInvoke(host, state);
            if (!(targetMethod.returnType() instanceof VoidTypeWrapper) && !targetMethod.returnType().equals(JavaClassWrapper.VOID_WRAPPER)) {
                state.assertStackSize(stackSize - toPop + 1);
            } else {
                state.assertStackSize(stackSize - toPop);
            }
        }
    }

    /**
     * Calls a method invocation log if the options contains {@link DebugOptions#REGISTER_METHOD_CALLS}
     * 
     * @param unit {@link CompilationUnit}
     * @param node {@link SimpleNode}
     * @param host the {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param argsNode the method arguments
     * @param targetMethod the target method
     */
    public static void logMethodInvocation(CompilationUnit unit, SimpleNode node, final MethodVisitor host, final CompilationState state,
            final BSHArguments argsNode, final MethodDescriptor targetMethod) {
        if (unit.options().debugOptions().contains(DebugOptions.REGISTER_METHOD_CALLS)) {
            host.visitVarInsn(Opcodes.ALOAD, 0);
            state.pushStack(unit.thisType());

            String sourceCode = unit.thisType().getSourceCodeFileName() == null ? "undefined" : unit.thisType().getSourceCodeFileName();
            host.visitLdcInsn(sourceCode);
            state.pushStack(String.class);

            if (targetMethod instanceof DynamicMethodDescriptor) {
                final DynamicMethodDescriptor dynMethod = (DynamicMethodDescriptor) targetMethod;
                host.visitLdcInsn(Type.getType(dynMethod.getTargetType().typeDescription()));
                host.visitLdcInsn(dynMethod.getMethodName());
                BytecodeHelper.pushIntConst(argsNode.jjtGetNumChildren(), host);
            } else {
                host.visitLdcInsn(Type.getType(targetMethod.declaringType().typeDescription()));
                host.visitLdcInsn(targetMethod.methodName());
                BytecodeHelper.pushIntConst(targetMethod.parameterTypes().size(), host);
            }
            state.pushStack(Class.class);
            state.pushStack(String.class);
            state.pushStack(int.class);

            BytecodeHelper.pushIntConst(argsNode.jjtGetNumChildren(), host);
            state.pushStack(int.class);

            BytecodeHelper.pushBooleanValue(!(targetMethod instanceof DynamicMethodDescriptor), host);
            state.pushStack(boolean.class);

            BytecodeHelper.pushIntConst(node.getLineNumber(), host);
            state.pushStack(int.class);

            LOG_METHOD_INVOCATION.writeInvoke(host, state);
        }
    }

    /**
     * Compiles a constructor call to the current class (this ( args ) ) or a super constructor call ( super( args ) )
     * 
     * @param host the {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param argsNode {@link BSHArguments}
     * @param nameNode {@link BSHAmbiguousName}
     * @throws CompilationFailedException if the compilation fails
     */
    private void compileConstructorCall(MethodVisitor host, CompilationState state, BSHArguments argsNode, BSHAmbiguousName nameNode)
            throws CompilationFailedException {
        state.addLineNumber(host, this.node.getLineNumber());
        int stack = state.getStackSize();
        TypeWrapper[] args = MethodCallCompiler.resolveParamTypes(argsNode, this.unit, state.currentScope());
        TypeWrapper targetType = "this".equals(nameNode) ? this.unit.thisType() : this.unit.thisType().superType();
        List<? extends TypeWrapper> ctorParams = this.unit.reflectionUtils().findConstructorFor(targetType, args);
        if (ctorParams == null) {
            throw new CompilationFailedException(this.node.getLineNumber(), "Cannot find constructor with args: " + Arrays.toString(args)
                    + " on type: " + targetType);
        }
        this.unit.thisType().loadThis(host, state);
        pushArgumentsFor(host, argsNode, ctorParams, state, unit, false); // varargs not supported...
        AllocationNodeCompiler.compileConstructorInvocation(host, state, targetType, ctorParams);
        state.popStack(2);
        state.assertStackSize(stack);
    }

    /**
     * returns true if the given node represents a constructor call
     * 
     * @param nameNode {@link BSHAmbiguousName}
     * @return boolean
     */
    private boolean isConstructorCall(BSHAmbiguousName nameNode) {
        return "this".equals(nameNode.text) || "super".equals(nameNode.text);
    }

    /**
     * Compiles an unqualified method call
     * 
     * @param host the {@link MethodVisitor}
     * @param state the {@link CompilationState}
     * @param argsNode the arguments
     * @param nameNode the method name
     * @throws CompilationFailedException e
     */
    private void compileUnqualifiedMethodCall(MethodVisitor host, CompilationState state, BSHArguments argsNode, BSHAmbiguousName nameNode)
            throws CompilationFailedException {
        final TypeWrapper[] params = resolveParamTypes(argsNode, this.unit, state.currentScope());
        final ReflectionUtils reflectionUtils = this.unit.reflectionUtils();
        LinkedList<ObjectAccessor> acc = new LinkedList<ObjectAccessor>();
        acc.add(new ThisObjectAccessor(this.unit.thisType()));
        MethodDescriptor targetMethod = findMethodWithEnclosingClass(nameNode, params, reflectionUtils, acc);
        if (targetMethod == null) {
            DynamicMethodDescriptor md = new DynamicMethodDescriptor(nameNode.text, unit.thisType());
            logMethodInvocation(unit, nameNode, host, state, argsNode, md);
            compileInvokeDynamic(host, state, argsNode, nameNode, md, md);
        } else {
            logMethodInvocation(unit, nameNode, host, state, argsNode, targetMethod);
            final boolean isStatic = Modifier.isStatic(targetMethod.modifiers());
            if (!isStatic) {
                for (ObjectAccessor objectAccessor : acc) {
                    objectAccessor.loadObject(host, state);
                }
            }
            final List<? extends TypeWrapper> paramTypes = targetMethod.parameterTypes();
            if (!paramTypes.isEmpty()) {
                pushArgumentsFor(host, argsNode, paramTypes, state, this.unit, targetMethod.isVarArgs());
            }
            state.addLineNumber(host, this.node.lastToken.beginLine);
            targetMethod.writeInvoke(host, state);
        }
    }

    /**
     * Finds a method on the enclosing type. If it can be found, will fill the given list of {@link ObjectAccessor} in order to properly
     * call the method
     * 
     * @param nameNode the name of the method
     * @param params the (static) parameter types
     * @param reflectionUtils {@link ReflectionUtils}
     * @param acc the list of object accessors. Must be ignored if this method returns null
     * @return a {@link MethodDescriptor} or null if none can be found
     */
    public MethodDescriptor findMethodWithEnclosingClass(final BSHAmbiguousName nameNode, final TypeWrapper[] params,
            final ReflectionUtils reflectionUtils, LinkedList<ObjectAccessor> acc) {
        TypeWrapper targetType = acc.getLast().type();
        MethodDescriptor targetMethod = reflectionUtils.findMethodForTypes(targetType, nameNode.text, params);
        if (targetMethod == null) {
            targetMethod = resolveMethodWithTransformation(targetType, nameNode.text, params, reflectionUtils);
        }
        if (targetMethod == null && targetType instanceof UnfinishedInnerType) {
            final UnfinishedInnerType inner = (UnfinishedInnerType) targetType;
            FieldDescriptor field = inner.getEnclosingInstanceField();

            if (field == null) {
                // it is a static inner class.
                acc.add(new ObjectAccessor() {

                    public TypeWrapper type() {
                        return inner.getEnclosingType();
                    }

                    public void storeObject(MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
                        throwError();
                    }

                    private void throwError() throws CompilationFailedException {
                        throw new CompilationFailedException(nameNode.getLineNumber(), "");
                    }

                    public boolean readOnly() {
                        return true;
                    }

                    public void preStore(MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
                        throwError();
                    }

                    public void loadObject(MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
                        throwError();
                    }
                });
            } else {
                acc.add(new FieldObjectAcessor(field));
            }
            targetMethod = findMethodWithEnclosingClass(nameNode, params, reflectionUtils, acc);
        }
        return targetMethod;
    }

    /**
     * Checks if this represents an unqualified method call
     * 
     * @param nameNode the name node
     * @return boolean
     */
    private boolean isUnqualifiedMethodCall(BSHAmbiguousName nameNode) {
        return !nameNode.text.contains(".");
    }

    /**
     * Compila instrucao de invoke dynamic
     * 
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param argsNode Argments
     * @param nameNode string
     * @param targetMethod method
     * @param dyn {@link DynamicMethodDescriptor}
     * @throws CompilationFailedException e
     */
    public static void compileInvokeDynamic(final MethodVisitor host, final CompilationState state, final BSHArguments argsNode,
            final BSHAmbiguousName nameNode, final MethodDescriptor targetMethod, DynamicMethodDescriptor dyn)
            throws CompilationFailedException {
        int oldStackSize = state.getStackSize();
        int id = state.getUnit().thisType().newDynamicInvocationId();
        host.visitVarInsn(Opcodes.ALOAD, 0);
        state.pushStack(state.getUnit().thisType());
        BytecodeHelper.pushIntConst(id, host);
        state.pushStack(int.class);
        // pushCallTarget(host, nameNode, state, JavaClassWrapper.OBJECT_WRAPPER);
        state.addLineNumber(host, nameNode.getLineNumber());
        final LinkedList<ObjectAccessor> links =
                resolveLinks(nameNode.text, state.currentScope(), nameNode.getLineNumber(), state.getUnit());
        if (links.size() == 1 && links.getFirst() instanceof ClassResolver) {
            // static invocation
            TypeWrapper typeWrapper = ((ClassResolver) links.getFirst()).getWrapped();
            host.visitLdcInsn(typeWrapper.toASMType());
            state.pushStack(links.getFirst().type());
        } else {
            for (final ObjectAccessor acc : links) {
                acc.loadObject(host, state);
            }
            state.getUnit().getCompilerManager().coerceToType(host, state.peekStack(), JavaClassWrapper.OBJECT_WRAPPER, state, 1);
        }

        host.visitLdcInsn(dyn.getMethodName());
        state.pushStack(String.class);
        // must wrap varargs call
        final int total = argsNode.jjtGetNumChildren();
        BytecodeHelper.pushIntConst(total, host);
        TypeWrapper targetType = JavaClassWrapper.OBJECT_WRAPPER;
        host.visitTypeInsn(Opcodes.ANEWARRAY, targetType.internalName());
        state.pushStack(new ArrayTypeWrapper(JavaClassWrapper.OBJECT_WRAPPER));
        for (int i = 0; i < total; i++) {
            host.visitInsn(Opcodes.DUP);
            state.pushStack(state.peekStack());
            BytecodeHelper.pushIntConst(i, host);
            state.pushStack(int.class);
            state.getUnit().compileNode(argsNode.getChild(i), host, state);
            state.getUnit().getCompilerManager().coerceToType(host, state.peekStack(), targetType, state);
            int opcode = targetType.toASMType().getOpcode(Opcodes.IASTORE);
            host.visitInsn(opcode);
            state.popStack(NUM_3);
        }
        state.assertStackSize(oldStackSize + NUM_3 + 2);
        state.addLineNumber(host, nameNode.lastToken.beginLine);
        targetMethod.writeInvoke(host, state);
    }

    /**
     * @param scope Scope
     * @return {@link MethodDescriptor}
     * @throws CompilationFailedException e
     */
    private MethodDescriptor resolveMethod(final Scope scope) throws CompilationFailedException {
        return this.resolveMethod(this.node.getNameNode(), this.node.getArgsNode(), scope);
    }

    /**
     * @param host {@link MethodVisitor}
     * @param argsNode args
     * @param paramTypes params
     * @param state state
     * @param unit unit
     * @param isVarargs boolean
     * @throws CompilationFailedException e
     */
    public static void pushArgumentsFor(final MethodVisitor host, final BSHArguments argsNode,
            final List<? extends TypeWrapper> paramTypes, final CompilationState state, CompilationUnit unit, boolean isVarargs)
            throws CompilationFailedException {
        if (paramTypes.isEmpty() && !isVarargs) {
            return;
        }
        TypeWrapper last = paramTypes.get(paramTypes.size() - 1);
        state.addLineNumber(host, argsNode.getLineNumber());
        final int total = argsNode.jjtGetNumChildren();
        ArrayTypeWrapper arrayType = null;
        if (last instanceof ArrayTypeWrapper) {
            // maybe varargs?
            arrayType = (ArrayTypeWrapper) last;
        }
        boolean varArgsCall = isVarargs && isVarArgsCall(argsNode, paramTypes, state, unit, total);
        int regularParams = varArgsCall ? paramTypes.size() - 1 : total;
        int expectedStackSize = state.getStackSize();
        for (int i = 0; i < regularParams; i++) {
            final SimpleNode child = argsNode.getChild(i);
            final NodeCompiler compiler = unit.getNodeCompiler(child);
            if (compiler == null) {
                throw new CompilationFailedException(argsNode.getLineNumber(), "cannot compile node: " + child + " of type: "
                        + child.getClass());
            }
            final TypeWrapper resultType = unit.getNodeType(child, state.currentScope());
            compiler.compile(host, state);
            state.assertStackSize(++expectedStackSize);
            unit.getCompilerManager().coerceToType(host, resultType, paramTypes.get(i), state);
        }
        if (varArgsCall) {
            int size = total - paramTypes.size() + 1;
            arrayType.newArray(state, host, size);
            TypeWrapper targetType = arrayType.getComponentType();
            for (int i = regularParams; i >= 0 && i < total; i++) {
                host.visitInsn(Opcodes.DUP);
                state.pushStack(state.peekStack());
                BytecodeHelper.pushIntConst(i - regularParams, host);
                state.pushStack(int.class);
                unit.compileNode(argsNode.getChild(i), host, state);
                unit.getCompilerManager().coerceToType(host, state.peekStack(), targetType, state);
                int opcode = targetType.toASMType().getOpcode(Opcodes.IASTORE);
                host.visitInsn(opcode);
                state.popStack(NUM_3);
            }
            state.assertStackSize(++expectedStackSize);
        }
    }

    /**
     * Checks if this is a var args call. It is considered so iff the parameters don't fit exactly in the method signature
     * 
     * @param argsNode the argument nodes
     * @param paramTypes the parameter types (of the calling method)
     * @param state the compilation state
     * @param unit the compilatio unit
     * @param total total number of parameters passed to the method
     * @return boolean
     * @throws CompilationFailedException if it cannot evaluate some argument type
     */
    protected static boolean isVarArgsCall(final BSHArguments argsNode, final List<? extends TypeWrapper> paramTypes,
            final CompilationState state, CompilationUnit unit, final int total) throws CompilationFailedException {
        if (paramTypes.size() != total) {
            return true;
        }
        for (int i = 0; i < total; i++) {
            final SimpleNode child = argsNode.getChild(i);
            final TypeWrapper resultType = unit.getNodeType(child, state.currentScope());
            if (unit.reflectionUtils().findTransformationMatching(resultType, paramTypes.get(i)) == null) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param host host
     * @param nameNode string
     * @param state state
     * @param methodTargetType type
     * @throws CompilationFailedException e
     */
    private static void pushCallTarget(final MethodVisitor host, final BSHAmbiguousName nameNode, final CompilationState state,
            final TypeWrapper methodTargetType) throws CompilationFailedException {
        state.addLineNumber(host, nameNode.getLineNumber());
        final LinkedList<ObjectAccessor> links =
                resolveLinks(nameNode.text, state.currentScope(), nameNode.getLineNumber(), state.getUnit());
        for (final ObjectAccessor acc : links) {
            acc.loadObject(host, state);
        }
        state.getUnit().getCompilerManager().coerceToType(host, state.peekStack(), methodTargetType, state, 1);
    }

    /**
     * @param nameNode node
     * @param argsNode node
     * @param scope scope
     * @return {@link MethodDescriptor}
     * @throws CompilationFailedException e
     */
    private MethodDescriptor resolveMethod(final BSHAmbiguousName nameNode, final BSHArguments argsNode, final Scope scope)
            throws CompilationFailedException {
        final String[] strings = nameNode.text.split("[.]");
        final LinkedList<ObjectAccessor> targetLinks = resolveLinks(nameNode.text, scope, nameNode.getLineNumber(), this.unit);
        final TypeWrapper methodTargetType = targetLinks.getLast().type();
        final String name = strings[strings.length - 1];

        final TypeWrapper[] params = resolveParamTypes(argsNode, this.unit, scope);
        final ReflectionUtils reflectionUtils = this.unit.reflectionUtils();
        MethodDescriptor targetMethod = reflectionUtils.findMethodForTypes(methodTargetType, name, params);
        if (targetMethod == null) {
            targetMethod = resolveMethodWithTransformation(methodTargetType, name, params, reflectionUtils);
            if (targetMethod == null) {
                // make dyn call
                return new DynamicMethodDescriptor(name, methodTargetType);
            }
        }
        if (isSuperCall(nameNode)) {
            final MethodDescriptor delegate = targetMethod;
            return new AbstractMethodDescriptor() {

                public List<? extends TypeWrapper> throwsClauses() {
                    return delegate.throwsClauses();
                }

                public TypeWrapper returnType() {
                    return delegate.returnType();
                }

                public List<? extends TypeWrapper> parameterTypes() {
                    return delegate.parameterTypes();
                }

                public int modifiers() {
                    return delegate.modifiers();
                }

                public String methodName() {
                    return delegate.methodName();
                }

                public boolean isVarArgs() {
                    return delegate.isVarArgs();
                }

                public TypeWrapper declaringType() {
                    return delegate.declaringType();
                }

                @Override
                protected int getOpcode() {
                    int superCall = super.getOpcode();
                    if (superCall == Opcodes.INVOKEVIRTUAL) {
                        return Opcodes.INVOKESPECIAL;
                    }
                    return superCall;
                }
            };
        }
        return targetMethod;
    }

    /**
     * @param nameNode {@link BSHAmbiguousName}
     * @return boolean
     */
    private boolean isSuperCall(BSHAmbiguousName nameNode) {
        String[] parts = nameNode.text.split("[.]");
        return parts.length == 2 && "super".equals(parts[0]);
    }

    /**
     * @param methodTargetType {@link TypeWrapper} o alvo da invocacao
     * @param name o nome do metodo
     * @param params os tipos dos parametros
     * @param reflectionUtils reflection
     * @return {@link MethodDescriptor} ou null
     */
    public static MethodDescriptor resolveMethodWithTransformation(final TypeWrapper methodTargetType, final String name,
            final TypeWrapper[] params, final ReflectionUtils reflectionUtils) {
        final Collection<TypeTransformation> transformations = reflectionUtils.findApplicableTransformations(methodTargetType);
        // trying type transformations
        for (final TypeTransformation typeTransformation : transformations) {
            final TypeWrapper newTgt = typeTransformation.toType(methodTargetType);
            MethodDescriptor targetMethod = reflectionUtils.findMethodForTypes(newTgt, name, params);
            if (targetMethod != null) {
                return targetMethod;
            }
        }
        return null;
    }

    /**
     * @param text text
     * @param scope scope
     * @param lineNumber int
     * @param unit unit
     * @return {@link LinkedList}
     * @throws CompilationFailedException e
     */
    public static LinkedList<ObjectAccessor> resolveLinks(final String text, final Scope scope, final int lineNumber,
            final CompilationUnit unit) throws CompilationFailedException {
        final String[] strings = text.split("[.]");
        ObjectAccessor resolver;
        final LinkedList<ObjectAccessor> targetLinks = new LinkedList<ObjectAccessor>();
        if (strings.length == 1) {
            if ("this".equals(strings[0])) {
                targetLinks.add(new ThisObjectAccessor(unit.thisType()));
                return targetLinks;
            }
            resolver = new ThisObjectAccessor(unit.thisType());
        } else {
            if ("this".equals(strings[0])) {
                resolver = new ThisObjectAccessor(unit.thisType());
            } else if ("super".equals(strings[0])) {
                resolver = new ReadOnlyObjectAccessor(lineNumber) {

                    public void loadObject(MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
                        if (state.currentScope().isStaticScope()) {
                            throw new CompilationFailedException(lineNumber, "tried to use 'super' on a static context");
                        }
                        visitor.visitVarInsn(Opcodes.ALOAD, 0);
                        state.pushStack(this.type());
                    }

                    public TypeWrapper type() {
                        return unit.thisType().superType();
                    }
                };
            } else {
                resolver = scope.getAccessor(strings[0]);
            }
        }

        if (resolver == null) {
            TypeWrapper type = scope.resolveType(strings[0]);
            if (type != null) {
                resolver = new ClassResolver(type);
            } else {
                LinkedList<ObjectAccessor> links = resolveLinksAsTypeLiteral(text, scope, lineNumber, unit);
                if (links != null) {
                    return links;
                } else if (unit.options().allowUndefinedVariables()) {
                    resolver = new DynamicFieldAccessor(new ThisObjectAccessor(unit.thisType()), unit.reflectionUtils(), strings[0]);
                } else {
                    throw new CompilationFailedException(lineNumber, "Undeclared variable reference: " + strings[0]);
                }
            }
        }

        targetLinks.add(resolver);
        boolean typeTried = false;
        for (int i = 1; i < strings.length - 1; i++) {
            final String fieldName = strings[i];
            final FieldDescriptor field = resolveField(resolver.type(), fieldName, unit);
            if (field == null) {
                if (!typeTried) {
                    LinkedList<ObjectAccessor> links = resolveLinksAsTypeLiteral(text, scope, lineNumber, unit);
                    typeTried = true;
                    if (links != null) {
                        return links;
                    }
                }
                // must also resort to dynamic fields
                resolver = new DynamicFieldObjectAccessor(fieldName);
            } else {
                resolver = new FieldObjectAcessor(field);
            }
            targetLinks.add(resolver);
        }
        return targetLinks;
    }

    /**
     * @author takeshi
     */
    public abstract static class ReadOnlyObjectAccessor implements ObjectAccessor {

        private final int lineNumber;

        /**
         * Constructor
         * 
         * @param lineNumber the declared line number
         */
        public ReadOnlyObjectAccessor(int lineNumber) {
            super();
            this.lineNumber = lineNumber;
        }

        /**
         * {@inheritDoc}
         */
        public void storeObject(MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
            throwError();
        }

        /**
         * Simply throws an excption
         * 
         * @throws CompilationFailedException e
         */
        protected void throwError() throws CompilationFailedException {
            throw new CompilationFailedException(lineNumber, "readonly");
        }

        /**
         * {@inheritDoc}
         */
        public boolean readOnly() {
            return true;
        }

        /**
         * {@inheritDoc}
         */
        public void preStore(MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
            throwError();
        }

    }

    /**
     * @param text text
     * @param scope scope
     * @param lineNumber int
     * @param unit unit
     * @return {@link LinkedList}
     * @throws CompilationFailedException e
     */
    private static LinkedList<ObjectAccessor> resolveLinksAsTypeLiteral(final String text, final Scope scope, final int lineNumber,
            CompilationUnit unit) throws CompilationFailedException {
        final String[] strings = text.split("[.]");
        // we now iterate until find a proper type
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < strings.length - 1; i++) {
            sb.append(strings[i]);
            TypeWrapper wrapper = scope.resolveType(sb.toString());
            if (wrapper != null) {
                // must resolve static links and method calls from here
                return resolveLinksFromStaticType(wrapper, copyFrom(strings, i + 1), scope, lineNumber, unit);
            }
            sb.append(".");
        }
        return null;
    }

    /**
     * Resolves links from static types
     * 
     * @param wrapper the {@link TypeWrapper}
     * @param suffix the remaining nodes
     * @param scope the declared scope
     * @param lineNumber the line number
     * @param unit2 the {@link CompilationUnit}
     * @return {@link LinkedList}
     * @throws CompilationFailedException e
     */
    private static LinkedList<ObjectAccessor> resolveLinksFromStaticType(TypeWrapper wrapper, String[] suffix, Scope scope, int lineNumber,
            CompilationUnit unit2) throws CompilationFailedException {
        // ok so now we must find a chain of fields ending with a method
        final LinkedList<ObjectAccessor> targetLinks = new LinkedList<ObjectAccessor>();
        ObjectAccessor resolver = new ClassResolver(wrapper);
        targetLinks.add(resolver);
        for (int i = 0; i < suffix.length - 1; i++) {
            String fieldName = suffix[i];
            final FieldDescriptor field = resolveField(resolver.type(), fieldName, unit2);
            if (field == null) {
                throw new CompilationFailedException(lineNumber, "No field named: " + fieldName + " acessible on type: " + resolver.type());
            }
            if (!field.isStatic()) {
                throw new CompilationFailedException(lineNumber, "Trying to access the non static field: " + resolver.type() + "."
                        + fieldName + " from a static context ");
            }
            resolver = new FieldObjectAcessor(field);
            targetLinks.add(resolver);
        }
        return targetLinks;
    }

    /**
     * Copies an array from a given position
     * 
     * @param strings the original array
     * @param i the start index
     * @return a shrinked copy of the array
     */
    public static String[] copyFrom(String[] strings, int i) {
        int len = strings.length - i;
        String[] copy = new String[len];
        System.arraycopy(strings, i, copy, 0, len);
        return copy;
    }

    /**
     * @param typeWrapper type
     * @param fieldName string
     * @param unit unit
     * @return field
     */
    public static FieldDescriptor resolveField(final TypeWrapper typeWrapper, final String fieldName, CompilationUnit unit) {
        for (FieldDescriptor fieldDescriptor : typeWrapper.visibleFields()) {
            if (fieldDescriptor.fieldName().equals(fieldName)) {
                return fieldDescriptor;
            }
        }
        return null;
    }

    /**
     * Resolve os parametros
     * 
     * @param argsNode args
     * @param unit unit
     * @param scope scope
     * @return type
     * @throws CompilationFailedException e
     */
    public static TypeWrapper[] resolveParamTypes(final BSHArguments argsNode, final CompilationUnit unit, final Scope scope)
            throws CompilationFailedException {
        final TypeWrapper[] argsTypes = new TypeWrapper[argsNode.jjtGetNumChildren()];
        for (int i = 0; i < argsTypes.length; i++) {
            final SimpleNode child = argsNode.getChild(i);
            TypeWrapper nodeType;
            if (child instanceof BSHType) {
                nodeType = TypedVariableDeclarationCompiler.resolveTypeForNode((BSHType) child, scope);
            } else {
                nodeType = unit.getNodeType(child, scope);
            }
            if (nodeType == null) {
                throw new CompilationFailedException(argsNode.getLineNumber(), "cannot find node type for node: " + child);
            }
            argsTypes[i] = nodeType;
        }
        return argsTypes;
    }

    /**
     * @return node
     */
    public BSHMethodInvocation getNode() {
        return this.node;
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper nodeResultType(final Scope scope) throws CompilationFailedException {
        return this.resolveMethod(scope).returnType();
    }

    /**
     * @author takeshi
     */
    public static class DynamicMethodDescriptor extends AbstractMethodDescriptor {

        private final String methodName;

        private final TypeWrapper targetType;

        /**
         * @param methodName string
         * @param methodTargetType the target method
         */
        public DynamicMethodDescriptor(String methodName, TypeWrapper methodTargetType) {
            super();
            this.methodName = methodName;
            this.targetType = methodTargetType;
        }

        /**
         * {@inheritDoc}
         */
        public TypeWrapper declaringType() {
            return JavaClassWrapper.wrapperFor(CompiledScript.class);
        }

        /**
         * {@inheritDoc}
         */
        public boolean isVarArgs() {
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public String methodName() {
            return "invokeDynamic";
        }

        /**
         * {@inheritDoc}
         */
        public int modifiers() {
            return Modifier.PUBLIC | Modifier.FINAL;
        }

        /**
         * {@inheritDoc}
         */
        public List<? extends TypeWrapper> parameterTypes() {
            return Arrays.asList(JavaClassWrapper.INT_WRAPPER, JavaClassWrapper.OBJECT_WRAPPER, JavaClassWrapper.wrapperFor(String.class),
                    new ArrayTypeWrapper(JavaClassWrapper.OBJECT_WRAPPER));
        }

        /**
         * {@inheritDoc}
         */
        public TypeWrapper returnType() {
            return JavaClassWrapper.OBJECT_WRAPPER;
        }

        /**
         * {@inheritDoc}
         */
        public List<? extends TypeWrapper> throwsClauses() {
            return Collections.emptyList();
        }

        /**
         * @return the methodName
         */
        public String getMethodName() {
            return this.methodName;
        }

        /**
         * @return the targetType
         */
        public TypeWrapper getTargetType() {
            return targetType;
        }

    }

}
